/**
 * 加密模型类
 */
const CryptoModel = class {
    /**
     * ES2022+：
     * #：表示私有的
     */

    /**
     * 算法名
     * @type {string}
     */
    static #Algorithm_Name_AEC_CBC = 'AES-CBC';

    /**
     * 算法长度
     * @type {number}
     */
    static #Algorithm_Length_256 = 256;

    /**
     * 密钥导入/导出格式
     * @type {Exclude<KeyFormat, "jwk">}
     */
    static #Crypto_Key_Format_Raw = 'raw';

    /**
     * 密钥是否可导出
     * @type {boolean}
     */
    static #Crypto_Key_Extractable = true;

    /**
     * 密钥用途，加密/解密
     * @type {KeyUsage[]}
     */
    static #Crypto_Key_Usage = ['encrypt', 'decrypt'];

    /**
     * 密文分隔符，加密向量和加密内容
     * @type {string}
     */
    static #Cipher_Split = ']sjoj=';

    /**
     * 构造函数
     */
    constructor() {
    }

    /**
     * Uint8Array数组转为Base64（Ascii）字符串
     * @param array {Uint8Array}
     * @returns {string}
     */
    static #Uint8ArrayToBase64(array) {
        // 将Uint8Array数组转为二进制字符串
        const binaryStr = String.fromCharCode(...array);
        // btoa()将二进制字符串编码为base64字符串
        return btoa(binaryStr);
    }

    /**
     * Base64（Ascii）字符串转为Uint8Array数组
     * @param base64 {string}
     * @returns {Uint8Array}
     */
    static #Base64ToUint8Array(base64) {
        // atob()将base64字符串解码为二进制字符串
        const binaryStr = atob(base64);
        // 将二进制字符串转为Uint8Array数组
        const array = new Uint8Array(binaryStr.length);
        for (let i = 0; i < binaryStr.length; i++) {
            array[i] = binaryStr.charCodeAt(i);
        }
        // 返回Uint8Array数组
        return array;
    }

    /**
     * Base64（Ascii）字符串转为CryptoKey的Promise
     * @param key {string}
     * @returns {Promise<CryptoKey>}
     */
    static async #Base64ToCryptoKey(key) {
        // 获取ArrayBuffer对象，
        let keyBuffer = this.#Base64ToUint8Array(key).buffer;
        // 当ArrayBufferLike表示的不是ArrayBuffer类型，转为ArrayBuffer
        if (!(keyBuffer instanceof ArrayBuffer)) {
            const arrayBuffer = new ArrayBuffer(keyBuffer.byteLength);
            // 通过Uint8Array为arrayBuffer设置值
            new Uint8Array(arrayBuffer).set(new Uint8Array(keyBuffer))
            keyBuffer = arrayBuffer;
        }
        // 返回密钥
        return await crypto.subtle.importKey(
            this.#Crypto_Key_Format_Raw, // 密钥导入格式
            keyBuffer, // 密钥arrayBuffer
            {name: this.#Algorithm_Name_AEC_CBC, length: this.#Algorithm_Length_256}, // 算法
            this.#Crypto_Key_Extractable, // 是否可导出
            this.#Crypto_Key_Usage // 用途
        );
    }

    /**
     * 数据加密
     * @param plaintext {string} 待加密的明文数据
     * @returns {Promise<string>} 明文加密后的密文Promise
     */
    static async encryptData(plaintext) {
        // 明文数据源，TextEncoder默认utf8
        const bufferSource = new TextEncoder().encode(plaintext);
        // 生成密钥
        const cryptoKey = await crypto.subtle.generateKey(
            {name: this.#Algorithm_Name_AEC_CBC, length: this.#Algorithm_Length_256}, // 算法
            this.#Crypto_Key_Extractable, // 是否可导出
            this.#Crypto_Key_Usage // 用途
        );
        // 设置初始化向量
        const iv = crypto.getRandomValues(new Uint8Array(16));
        // 数据加密
        const encrypted = await crypto.subtle.encrypt(
            {name: this.#Algorithm_Name_AEC_CBC, iv: iv}, // 算法
            cryptoKey, //密钥
            bufferSource // 数据源
        );
        // 获取初始向量的base64编码
        const initVector = this.#Uint8ArrayToBase64(iv);
        // 导出密钥ArrayBuffer
        const keyBuffer = await crypto.subtle.exportKey(this.#Crypto_Key_Format_Raw, cryptoKey);
        // 获取密钥的base64编码
        const key = this.#Uint8ArrayToBase64(new Uint8Array(keyBuffer));
        // 获取加密后的数据的base64编码
        const ciphertext = this.#Uint8ArrayToBase64(new Uint8Array(encrypted));
        // 返回密文数据
        return key + this.#Cipher_Split + initVector + this.#Cipher_Split + ciphertext;
    }

    /**
     * 数据解密
     * @param cipher {string} 待解密的密文数据
     * @returns {Promise<string>} 密文解密后的明文数据Promise
     */
    static async decryptData(cipher) {
        // 密文分割
        const split = cipher.split(this.#Cipher_Split);
        // 获取密钥
        const cryptoKey = await this.#Base64ToCryptoKey(split[0]);
        // 获取初始化向量
        const iv = this.#Base64ToUint8Array(split[1]);
        // 获取密文数据源
        const bufferSource = this.#Base64ToUint8Array(split[2]);
        console.log("iv: ", iv, "\nencrypt: ", new Uint8Array(bufferSource));
        // 数据解密
        const decrypted = await crypto.subtle.decrypt(
            {name: this.#Algorithm_Name_AEC_CBC, iv: iv},
            cryptoKey,
            bufferSource,
        );
        // 返回明文数据，TextDecoder默认utf8
        return new TextDecoder('utf-8').decode(decrypted);
    }


}

CryptoModel.encryptData('这是加密测试语句。（This is a test-statement for crypto）').then(cipher => {
    console.log('Cipher: ', cipher);
    CryptoModel.decryptData(cipher).then(plaintext => {
        console.log('Plaintext: ', '-' + plaintext);
    })
});


